
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">















 
 
 
 
 
 
 
  
  


<html>
  <head>
    <script type="text/javascript" language="JavaScript">
    ORIGINAL_PAGE_PATH = "/appengine/articles/overheard.html";
    </script>
    
    
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Just Overheard It: An example application - Google App Engine - Google Code</title>
<script type="text/javascript"><!--
(function(){function a(){this.t={};this.tick=function(c){this.t[c]=(new Date).getTime()};this.tick("start")}var b=new a;window.jstiming={Timer:a,load:b};if(window.external&&window.external.pageT)window.jstiming.pt=window.external.pageT;})();

var _tocPath_ = '/appengine/docs/_toc.ezt';
var codesite_token = null;
//--></script>
<link href="../../css/codesite.pack.04102009.css" type="text/css" rel="stylesheet"></link>
<script src="../../js/codesite_head.pack.04102009.js" type="text/javascript"></script>
<script type="text/javascript">CODESITE_CSITimer['load'].tick('bhs');</script>
<link rel="search" type="application/opensearchdescription+xml" title="Google Code" href="http://code.google.com/osd.xml" />

<!--[if IE]><link rel="stylesheet" type="text/css" href="../../css/iehacks.css" /><![endif]-->

    <link href="../../css/semantic_headers.css" rel="stylesheet" type="text/css" />
    <link href="../css/local_extensions.css" rel="stylesheet" type="text/css" />
  </head>

  <body class="gc-documentation">

    
    
    
</a>

<div id="gb">
 <span>
  
    <a id="lang-dropdown" class="dropdown" href="http://code.google.com" onclick="return false;"><img class="globeicon" src="../../images/globe2_small.png"/><span>English</span></a>
  
 </span>
</div>

<div class="gbh" style="left: 0pt;"></div>
<div class="gbh" style="right: 0pt;"></div>


<style type="text/css">
  #gc-topnav h1 {
    padding: 0 0 0 6px;
  }
</style>


<div id="gc-container">
<a name="top"></a>
<div id="skipto">
  <a href="#gc-pagecontent-anchor">Skip to page content</a>
  <a href="#gc-toc-anchor">Skip to main navigation</a>
</div>

<div id="gc-header">
  <div id="logo"><a href="http://code.google.com">
  
  
     <img src="../../images/cleardot.gif" height="1px" width="1px" alt="Google Code Home Page" id="gc-logo-img"/>
  
  
  </a></div>
  <div id="search">
    <div id="searchForm" class="searchForm">
      <form id="cse" action="http://www.google.com/cse" accept-charset="utf-8" class="gsc-search-box" onsubmit="executeGSearch(document.getElementById('gsearchInput').value); return false;">
      <noscript>
      <input type="hidden" name="cref" value="http://code.google.com/cse/googlecode-context.xml"/>
      </noscript>
        <table class="gsc-search-box" cellpadding="0" cellspacing="0">
          <tbody>
            <tr>
              <td class="gsc-input">
                <input id="gsearchInput" type="text" name="q" maxlength="2048" class="gsc-input" autocomplete="off" title="Google Code Search" style="width:345px"/>
              </td>
              <td class="gsc-search-button">
                <div id="cs-searchresults" onclick="event.cancelBubble = true;"></div>
                <input title="Search" id="gsearchButton" class="gsc-search-button" name="sa" value="Search" type="submit"/>
              </td>
            </tr>
            <tr>
              <td colspan="2" class="greytext">e.g. "templates" or "datastore"</td>
            </tr>
          </tbody>
        </table>
      </form>
    </div> <!-- end searchForm -->
  </div> <!-- end search -->
</div> <!-- end gc-header -->


<div id="codesiteContent">

<a name="gc-topnav-anchor"></a>
<div id="gc-topnav">
  <h1>Google App Engine</h1>
  <ul id="articles" class="gc-topnav-tabs">

    <li id="home_link">
      <a href="../index.html" title="Google App Engine home page">Home</a>
    </li>
  
    <li id="docs_link">
      <a href="../docs/index.html" title="Official Google App Engine documentation">Docs</a>
    </li>
  
    <li id="faq_link">
      <a href="../kb/index.html" title="Answers to frequently asked questions about Google App Engine">FAQ</a>
    </li>
  
    <li id="articles_link">
      <a href="index.html" class="selected" title="Focused articles and tutorials for Google App Engine developers">Articles</a>
    </li>
  
    <li>
      <a href="http://googleappengine.blogspot.com/" title="Official Google App Engine blog">Blog</a>
    </li>
  
    <li>
      <a href="../community.html" title="Community home for Google App Engine">Community</a>
    </li>
  
    <li>
      <a href="../terms.html" title="Google App Engine terms of service">Terms</a>
    </li>
  
    <li>
      <a href="../downloads.html" title="Download Google App Engine">Download</a>
    </li>
  

  </ul>
</div> <!-- end gc-topnav -->

    <div class="g-section g-tpl-170">

      <a name="gc-toc-anchor"></a>  
      <div class="g-unit g-first" id="gc-toc">
        <ul>
  <li><a href="../downloads.html">Downloads</a></li>
  <li><a href="http://code.google.com/status/appengine">System Status</a></li>
  <li><a href="http://code.google.com/p/googleappengine/issues/list">Issue Tracker</a></li>
</ul>
<div class="line"></div>
<ul>
  <li><h2>Getting Started</h2>
    <ul>
      <li><a href="../docs/whatisgoogleappengine.html">What Is Google App Engine?</a></li>
      <li><a href="../docs/java/gettingstarted/index.html">Java</a>
        <ul>
              <li><a href="../docs/java/gettingstarted/introduction.html">Introduction</a></li>
    <li><a href="../docs/java/gettingstarted/installing.html">Installing the Java SDK</a></li>
    <li><a href="../docs/java/gettingstarted/creating.html">Creating a Project</a></li>
    <li><a href="../docs/java/gettingstarted/usingusers.html">Using the Users Service</a></li>
    <li><a href="../docs/java/gettingstarted/usingjsps.html">Using JSPs</a></li>
    <li><a href="../docs/java/gettingstarted/usingdatastore.html">Using the Datastore with JDO</a></li>
    <li><a href="../docs/java/gettingstarted/staticfiles.html">Using Static Files</a></li>
    <li><a href="../docs/java/gettingstarted/uploading.html">Uploading Your Application</a></li>

        </ul>
      </li>
      <li><a href="../docs/python/gettingstarted/index.html">Python</a>
        <ul>
            <li><a href="../docs/python/gettingstarted/introduction.html">Introduction</a></li>
  <li><a href="../docs/python/gettingstarted/devenvironment.html">The Development Environment</a></li>
  <li><a href="../docs/python/gettingstarted/helloworld.html">Hello, World!</a></li>
  <li><a href="../docs/python/gettingstarted/usingwebapp.html">Using the webapp Framework</a></li>
  <li><a href="../docs/python/gettingstarted/usingusers.html">Using the Users Service</a></li>
  <li><a href="../docs/python/gettingstarted/handlingforms.html">Handling Forms With webapp</a></li>
  <li><a href="../docs/python/gettingstarted/usingdatastore.html">Using the Datastore</a></li>
  <li><a href="../docs/python/gettingstarted/templates.html">Using Templates</a></li>
  <li><a href="../docs/python/gettingstarted/staticfiles.html">Using Static Files</a></li>
  <li><a href="../docs/python/gettingstarted/uploading.html">Uploading Your Application</a></li>

        </ul>
      </li>
    </ul>
  </li>
</ul>
<div class="line"></div>
<ul>
  <li><h2>Java <sup class="new">Early Look</sup></h2>
    <ul>
          <li><a href="../docs/java/overview.html">Overview</a></li>
    <li><a href="../docs/java/runtime.html">Servlet Environment</a></li>
    <li><a href="../docs/java/datastore/index.html">Storing Data</a>
      <ul>
            <li><a href="../docs/java/datastore/overview.html">Overview</a></li>
    <li><a href="../docs/java/datastore/usingjdo.html">Using JDO</a></li>
    <li><a href="../docs/java/datastore/dataclasses.html">Defining Data Classes</a></li>
    <li><a href="../docs/java/datastore/creatinggettinganddeletingdata.html">Creating, Getting and Deleting Data</a></li>
    <li><a href="../docs/java/datastore/queriesandindexes.html">Queries and Indexes</a></li>
    <li><a href="../docs/java/datastore/transactions.html">Transactions</a></li>
    <li><a href="../docs/java/datastore/relationships.html">Relationships</a></li>
    <li><a href="../docs/java/datastore/usingjpa.html">Using JPA</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/datastore/package-summary.html">Low-level API</a></li>

      </ul>
    </li>
    <li><a href="../docs/java/apis.html">Services</a>
      <ul>
        <li><a href="../docs/java/memcache/index.html">Memcache</a>
          <ul>
                <li><a href="../docs/java/memcache/overview.html">Overview</a></li>
    <li><a href="../docs/java/memcache/usingjcache.html">Using JCache</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/memcache/package-summary.html">Low-level API</a></li>

          </ul>
        </li>
        <li><a href="../docs/java/urlfetch/index.html">URL Fetch</a>
          <ul>
                <li><a href="../docs/java/urlfetch/overview.html">Overview</a></li>
    <li><a href="../docs/java/urlfetch/usingjavanet.html">Using java.net</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/urlfetch/package-summary.html">Low-level API</a></li>

          </ul>
        </li>
        <li><a href="../docs/java/mail/index.html">Mail</a>
          <ul>
                <li><a href="../docs/java/mail/overview.html">Overview</a></li>
    <li><a href="../docs/java/mail/usingjavamail.html">Using JavaMail</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/mail/package-summary.html">Low-level API</a></li>

          </ul>
        </li>
        <li><a href="../docs/java/images/index.html">Images</a>
          <ul>
                <li><a href="../docs/java/images/overview.html">Overview</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/images/package-summary.html">API Reference</a></li>

          </ul>
        </li>
        <li><a href="../docs/java/users/index.html">Google Accounts</a>
          <ul>
                <li><a href="../docs/java/users/overview.html">Overview</a></li>
    <li><a href="../docs/java/javadoc/com/google/appengine/api/users/package-summary.html">API Reference</a></li>

          </ul>
        </li>
      </ul>
    </li>
    <li><a href="../docs/java/javadoc/index.html">Javadoc Reference</a></li>
    <li><a href="../docs/java/jrewhitelist.html">JRE Class White List</a></li>
    <li><a href="../docs/java/config/index.html">Configuration</a>
      <ul>
            <li><a href="../docs/java/config/webxml.html">Deployment Descriptor</a></li>
    <li><a href="../docs/java/config/appconfig.html">App Config</a></li>
    <li><a href="../docs/java/config/indexconfig.html">Index Config</a></li>
    <li><a href="../docs/java/config/cron.html">Scheduled Tasks</a></li>

      </ul>
    </li>
    <li><a href="../docs/java/tools/index.html">Tools</a>
      <ul>
            <li><a href="../docs/java/tools/devserver.html">Development Server</a></li>
    <li><a href="../docs/java/tools/uploadinganapp.html">Uploading and Managing</a></li>
    <li><a href="../docs/java/tools/eclipse.html">Google Plugin for Eclipse</a></li>
    <li><a href="../docs/java/tools/ant.html">Using Apache Ant</a></li>

      </ul>
    </li>
    <li><a href="../docs/java/howto/index.html">How-To</a>
      <ul>
              <li><a href="../docs/java/howto/unittesting.html">Unit Testing</a></li>

      </ul>
    </li>

    </ul>
  </li>
</ul>
<div class="line"></div>
<ul>
  <li><h2>Python</h2>
    <ul>
          <li><a href="../docs/python/overview.html">Overview</a></li>
    <li><a href="../docs/python/runtime.html">CGI Environment</a></li>
    <li><a href="../docs/python/datastore/index.html">Storing Data</a>
      <ul>
             <li><a href="../docs/python/datastore/overview.html">Overview</a></li>
     <li><a href="../docs/python/datastore/entitiesandmodels.html">Entities and Models</a></li>
     <li><a href="../docs/python/datastore/creatinggettinganddeletingdata.html">Creating, Getting and Deleting Data</a></li>
     <li><a href="../docs/python/datastore/keysandentitygroups.html">Keys and Entity Groups</a></li>
     <li><a href="../docs/python/datastore/queriesandindexes.html">Queries and Indexes</a></li>
     <li><a href="../docs/python/datastore/transactions.html">Transactions</a></li>
     <li><a href="../docs/python/datastore/typesandpropertyclasses.html">Types and Property Classes</a></li>
     <li><a href="../docs/python/datastore/gqlreference.html">GQL Reference</a></li>

     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/datastore/modelclass.html">Model</a></li>
         <li><a href="../docs/python/datastore/expandoclass.html">Expando</a></li>
         <li><a href="../docs/python/datastore/polymodelclass.html">PolyModel</a></li>
         <li><a href="../docs/python/datastore/propertyclass.html">Property</a></li>
         <li><a href="../docs/python/datastore/queryclass.html">Query</a></li>
         <li><a href="../docs/python/datastore/gqlqueryclass.html">GqlQuery</a></li>
         <li><a href="../docs/python/datastore/keyclass.html">Key</a></li>
         <li><a href="../docs/python/datastore/functions.html">Functions</a></li>
         <li><a href="../docs/python/datastore/exceptions.html">Exceptions</a></li>
       </ul>
     </li>

      </ul>
    </li>
    <li><a href="../docs/python/apis.html">Services</a>
      <ul>
        <li><a href="../docs/python/memcache/index.html">Memcache</a>
          <ul>
                 <li><a href="../docs/python/memcache/overview.html">Overview</a></li>
      <li><a href="../docs/python/memcache/usingmemcache.html">Using Memcache</a></li>
     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/memcache/clientclass.html">Client</a></li>
         <li><a href="../docs/python/memcache/functions.html">Functions</a></li>
       </ul>
     </li>

          </ul>
        </li>
        <li><a href="../docs/python/urlfetch/index.html">URL Fetch</a>
          <ul>
                 <li><a href="../docs/python/urlfetch/overview.html">Overview</a></li>
     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/urlfetch/fetchfunction.html">The fetch Function</a></li>
         <li><a href="../docs/python/urlfetch/responseobjects.html">Response Objects</a></li>
         <li><a href="../docs/python/urlfetch/exceptions.html">Exceptions</a></li>
       </ul>
     </li>

          </ul>
        </li>
        <li><a href="../docs/python/mail/index.html">Mail</a>
          <ul>
                 <li><a href="../docs/python/mail/overview.html">Overview</a></li>
     <li><a href="../docs/python/mail/sendingmail.html">Sending Mail</a></li>
     <li><a href="../docs/python/mail/attachments.html">Attachments</a></li>
     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/mail/emailmessageclass.html">EmailMessage</a></li>
         <li><a href="../docs/python/mail/emailmessagefields.html">Message Fields</a></li>
         <li><a href="../docs/python/mail/functions.html">Functions</a></li>
         <li><a href="../docs/python/mail/exceptions.html">Exceptions</a></li>
       </ul>
     </li>

          </ul>
        </li>
        <li><a href="../docs/python/images/index.html">Images</a>
          <ul>
                 <li><a href="../docs/python/images/overview.html">Overview</a></li>
     <li><a href="../docs/python/images/installingPIL.html">Installing PIL</a></li>
     <li><a href="../docs/python/images/usingimages.html">Using the Images API</a></li>
     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/images/imageclass.html">Image</a></li>
         <li><a href="../docs/python/images/functions.html">Functions</a></li>
         <li><a href="../docs/python/images/exceptions.html">Exceptions</a></li>
       </ul>
     </li>

          </ul>
        </li>
        <li><a href="../docs/python/users/index.html">Google Accounts</a>
          <ul>
                 <li><a href="../docs/python/users/overview.html">Overview</a></li>
     <li><a href="../docs/python/users/userobjects.html">User Objects</a></li>
     <li><a href="../docs/python/users/loginurls.html">Login URLs</a></li>
     <li><a href="../docs/python/users/adminusers.html">Admin Users</a></li>

     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/users/userclass.html">User</a></li>
         <li><a href="../docs/python/users/functions.html">Functions</a></li>
         <li><a href="../docs/python/users/exceptions.html">Exceptions</a></li>
       </ul>
     </li>

          </ul>
        </li>
      </ul>
    </li>
    <li><a href="../docs/python/config/index.html">Configuration</a>
      <ul>
            <li><a href="../docs/python/config/appconfig.html">App Config</a></li>
    <li><a href="../docs/python/config/indexconfig.html">Index Config</a></li>
    <li><a href="../docs/python/config/cron.html">Scheduled Tasks</a></li>

      </ul>
    </li>
    <li><a href="../docs/python/tools/index.html">Tools</a>
      <ul>
            <li><a href="../docs/python/tools/devserver.html">Development Server</a></li>
    <li><a href="../docs/python/tools/uploadinganapp.html">Uploading and Managing</a></li>
    <li><a href="../docs/python/tools/uploadingdata.html">Uploading Data</a></li>
    <li><a href="../docs/python/tools/webapp/index.html">webapp Framework</a>
      <ul>
             <li><a href="../docs/python/tools/webapp/overview.html">Overview</a></li>
     <li><a href="../docs/python/tools/webapp/running.html">Running the Application</a></li>
     <li><a href="../docs/python/tools/webapp/requesthandlers.html">Request Handlers</a></li>
     <li><a href="../docs/python/tools/webapp/requestdata.html">Request Data</a></li>
     <li><a href="../docs/python/tools/webapp/buildingtheresponse.html">Building the Response</a></li>
     <li><a href="../docs/python/tools/webapp/redirects.html">Redirects, Headers and Status Codes</a></li>
     

     <li><span class="tlw-title tlw-expanded">Reference</span>
       <ul>
         <li><a href="../docs/python/tools/webapp/requestclass.html">Request</a></li>
         <li><a href="../docs/python/tools/webapp/responseclass.html">Response</a></li>
         <li><a href="../docs/python/tools/webapp/requesthandlerclass.html">RequestHandler</a></li>
         <li><a href="../docs/python/tools/webapp/wsgiapplicationclass.html">WSGIApplication</a></li>
         <li><a href="../docs/python/tools/webapp/utilmodule.html">Utility Functions</a></li>
         
       </ul>
     </li>

      </ul>
    </li>
    <li><a href="../docs/python/tools/libraries.html">Third-party Libraries</a></li>

      </ul>
    </li>
    <li><a href="../docs/python/howto/index.html">How-To</a>
      <ul>
              <li><a href="../docs/python/howto/usinggdataservices.html">Google Data Services</a></li>

      </ul>
    </li>

    </ul>
  </li>
</ul>
<div class="line"></div>
<ul>
  <li><h2>Managing Your App</h2>
    <ul>
      <li><a href="../docs/theadminconsole.html">The Admin Console</a></li>
      <li><a href="../docs/quotas.html">Quotas</a></li>
      <li><a href="../docs/billing.html">Billing</a></li>
    </ul>
  </li>
</ul>
<div class="line"></div>
<ul>
  <li><h2>Resources</h2>
    <ul>
      <li><a href="../kb/index.html">FAQ</a></li>
      <li><a href="index.html">Articles</a></li>
      <li><a href="http://appengine-cookbook.appspot.com/">Cookbook</a></li>
      <li><a href="http://appgallery.appspot.com/">App Gallery</a></li>
      <li><a href="http://code.google.com/p/googleappengine/">SDK Code</a></li>
      <li><a href="http://code.google.com/p/google-app-engine-samples/">Sample Apps Code</a></li>
      <li><a href="../community.html">Discussion Groups</a></li>
    </ul>
  </li>
</ul>
<div class="line"></div>
<ul>
  <li><a href="../docs/roadmap.html">Product Roadmap</a></li>
  <li><a href="http://code.google.com/p/googleappengine/wiki/SdkReleaseNotes">Release Notes</a></li>
  <li><a href="../docs/revision_history.html">Revision History</a></li>
</ul>

        <a class="hidden" href="#gc-topnav-anchor">More Google App Engine resource links</a>
      </div>
      
      <a name="gc-pagecontent-anchor"></a>   
      <div class="g-unit" id="gc-pagecontent">
        <script type="text/javascript">CODESITE_docEarlyProcessing();</script>
        <h1 class="page_title">Just Overheard It: An example application</h1>


<i>Joe Gregorio</i><br>
<i>February 13, 2008</i>

<div id="jd-content">
<div class="jd-descr">

<h2 id="overview">
  Overview
</h2>
<p>
  There's plenty of sites that keep track of the most popular of something on a
  daily basis, such as delicious, digg, reddit, flickr, etc. They keep track of
  the days most popular items, where popularity is recorded implicitly,
  recording a link in delicious, or explicitly, voting up and down on reddit.
  One of the interesting aspects of these sites is "decay", the drifting down
  in the rankings of older items so newer items can rise to the top. Overheard
  is an example application for Google App Engine that demonstrates both voting
  and decay, using amusing quotes as the subject. It also demonstrates
  optimizations such as using memcache and the AJAX Libraries API. Obviously
  not a world rocking application, but the principles outlined can be applied
  to many different kinds of applications.
</p>

<p>
  Our basic model will be to have Quotes, which contain a string for the
  quotation, and Votes, which contain the user name and vote for a particular
  user.
</p>
<p>
  Here are the models for the program:
</p>
<p>
  <b>Models</b>
</p>

<pre class="prettyprint">class Quote(db.Model):
  """Storage for a single quote and its metadata

  Properties
    quote:          The quote as a string
    uri:            An optional URI that is the source of the quotation
    rank:           A calculated ranking based on the number of votes and
                       when the quote was added.
    created:        When the quote was created, recorded in the number of
                       days since the beginning of our local epoch.
    creation_order: Totally unique index on all quotes in order of their
                       creation.
    creator:        The user that added this quote.
  """
  quote = db.StringProperty(required=True, multiline=True)
  uri   = db.StringProperty()
  rank = db.StringProperty()
  created = db.IntegerProperty(default=0)
  creation_order = db.StringProperty(default=" ")
  votesum = db.IntegerProperty(default=0)
  creator = db.UserProperty()


class Vote(db.Model):
  """Storage for a single vote by a single user on a single quote.


  Index
    key_name: The email address of the user that voted.
    parent:   The quote this is a vote for.

  Properties
    vote: The value of 1 for like, -1 for dislike.
  """
  vote = db.IntegerProperty(default=0)


class Voter(db.Model):
  """Storage for metadata about each user

  Properties
    count:          An integer that gets incremented with users addition of a quote.
                      Used to build a unique index for quote creation.
    hasVoted:       Has this user ever voted on a quote.
    hasAddedQuote:  Has this user ever added a quote.
  """
  count = db.IntegerProperty(default=0)
  hasVoted = db.BooleanProperty(default=False)
  hasAddedQuote = db.BooleanProperty(default=False)
</pre>

<h2 id="voting">Votes</h2>
<p>
  One of the most common optimizations done in App Engine to create a
  performant application is to precompute values at write time as opposed to
  read time. We will do that in this case and will calculate the vote total and
  rank for a Quote each time a vote is cast. That slows down writes just a
  little, but vastly speeds up reading, and makes our searches possible. That
  is, if we want to present a page with the Quotes ordered by their ranking, we
  need a <code>rank</code> property that is up to date to sort on. If we want to keep the
  vote count consistent we are going to have to add in a vote and modify the
  sum of votes for that quote in the same transaction. For that to work both
  the quote and the vote need to be in the same entity group, and we accomplish
  that by making each vote a child of its associated quote. There's more
  details in the documentation on <a
  href="http://code.google.com/appengine/docs/python/datastore/transactions.html">entity
  groups and transactions</a>.  </p>
<p>
  With each vote a child of its associated quote we can put voting into a
  transaction where the <code>Quote.votesum</code> and <code>Quote.rank</code>
  are updated at the same time that the users vote is recorded.  </p>
<p>
  <b>models.set_vote()</b>
</p>
<pre class="prettyprint">def set_vote(quote_id, user, newvote):
  """
  Record 'user' casting a 'vote' for a quote with an id of 'quote_id'.
  The 'newvote' is usually an integer in [-1, 0, 1].
  """
  if user is None:
    return
  email = user.email()

  def txn():
    quote = Quote.get_by_id(quote_id)
    vote = Vote.get_by_key_name(key_names = user.email(), parent = quote)
    if vote is None:
      vote = Vote(key_name = user.email(), parent = quote)
    if vote.vote == newvote:
      return
    quote.votesum = quote.votesum - vote.vote + newvote
    vote.vote = newvote
    # See the docstring of main.py for an explanation of
    # the following formula.
    quote.rank = "%020d|%s" % (
      long(quote.created * DAY_SCALE + quote.votesum),
      quote.creation_order
      )
    db.put([vote, quote])
    memcache.set("vote|" + user.email() + "|" + str(quote_id), vote.vote)

  db.run_in_transaction(txn)
  _set_progress_hasVoted(user)
</pre>
<p>
  Note that we need to specify the parent of the vote when querying for it, and
  we also need to specify the parent when creating a Vote.  </p>


<h2 id="decay">
  Decay
</h2>
<p>
  The value of the <code>Quote.rank</code> property is what we are focused on now. The <code>rank</code>
is what we will sort by to show the most popular items and it will need
to be calculated in a way that allows newer items to eventually float
to the top of the list even though older items may have received more
votes, otherwise our site will become very boring very fast. We don't
have background tasks that can go back and adjust the current rankings
of quotes over time, so we need a way to rank quotes that puts fresher
quotes higher in the ranking in a scalable way that will work for a
long period of time.
</p>
<p> The insight here is that we can add <code>weight</code> to a vote based on
when the Quote was added to the system. The rank for each quote is
calculated as:
</p>
<pre><code>rank = created * DAY_SCALE + votesum</code></pre>
<p>
  Where the variables have the following meanings:
</p>
<dl><dt><code>created</code></dt>
<dd>Number of days after 1/1/2008 that the Quote was created.</dd>
<dt><code>votesum</code></dt>
<dd>Sum of all +1 and -1 votes for a quote.</dd>
<dt><code>DAY_SCALE</code></dt>
<dd>This
is a constant that determines how quickly votes should decay (defaults
to 4). Another way to think about this is how many votes one more day
is worth. </dd>
</dl>
<p> For 'created' we just pick a date in the past as our day zero and
start counting forward from there. <code>DAY_SCALE</code> with a value of four means
that a vote for a Quote one day fresher will be worth four more votes.
That is, if Quote 1 and 2 have the same number of votes but Quote 2 was
added a day later then its rank will be 4 larger.
</p>
<p>
  Does this work? Presume the following scenario:
</p>
<p><b>Day 1</b></p>
<pre>
 Quote 0 and 1 are added on Day 1 and
 get 5 and 3 votes respectively.
 Order by rank is [Quote 0, Quote 1].

 Quote Votes Rank
 --------------------------------
 0 (5) 1 * 4 + 5 = 9
 1 (3) 1 * 4 + 3 = 7
</pre>

<p><b>Day 2</b></p>
<pre>
 Quote 0 and 1 get 3 and 0 more votes respectively.
 Quote 2 is added and gets 3 votes.
 Order by rank is now [Quote 0, Quote 2, Quote 1]

 Quote Votes Rank
 --------------------------------------
 0 (5) + (3) 1 * 4 + 8 = 12
 1 (3) + (0) 1 * 4 + 3 = 7
 2 (3) 2 * 4 + 3 = 11
</pre>

<p><b>Day 3</b></p>
<pre>
 Quote 2 gets two more vote. Quote 3 is added and gets 5 votes.
 Order by rank is [Quote 3, Quote 2, Quote 0, Quote 1]

 Quote Votes Rank
 -------------------------------------------
 0 (5) + (3) 1 * 4 + 8 = 12
 1 (3) + (0) 1 * 4 + 3 = 7
 2 (3) + (2) 2 * 5 + 4 = 14
 3 (5) 3 * 4 + 5 = 17
</pre>
<p> Note that ties are possible, which means that rank for quotes will
have to be disambiguated since the application allows paging of ranked
quotes. That means for each rank we need to append information that
will make it unique. In this case we will keep one counter per user and
increment it each time that user adds a new quote to the system. The
rank plus the user id and counter value is guaranteed to be unique for
each quote.
</p>
<p>
  Here is the code for generating a unique value based on the user:
</p>
<p>
  <b>models._unique_user()</b>
</p>
<pre class="prettyprint">
def _unique_user(user):
  """
  Creates a unique string by using an increasing
  counter sharded per user. The resulting string
  is hashed to keep the users email address private.
  """

  def txn():
    voter = _get_or_create_voter(user)
    voter.count += 1
    voter.hasAddedQuote = True
    voter.put()
    return voter.count

  count = db.run_in_transaction(txn)

  return hashlib.md5(user.email() + "|" + str(count)).hexdigest()
</pre>
<p>
  For more information on paging see the article <a
  href="http://code.google.com/appengine/articles/paging.html">"How-To Do
  Paging on App Engine."</a>
</p>
<h2 id="contention">Contention
</h2>
<p>
  One of the most important design principles to keep in mind in building a
  performant App Engine application is to avoid contention, trying to update a
  single entity or entity group too quickly. Any single entity or entity group
  can only be updated about five times a second. Since a single quote and its
  associated votes form a single entity group we only need to consider how
  often any one quote is updated when looking for contention. In this
  application we'll assume that on the high side there will be a couple
  thousand votes per quote over the course of a day, which averages out to much
  less than five votes per second, so we are in good shape.  </p>
<h2 id="memcache">
  Using memcache
</h2>
<p>
  There is a memcache API that is very useful for speeding up an application
  and avoiding hitting the datastore. One of the most common queries in the
  Overhead application is pulling out each user's votes for a list of quotes so
  they are reflected in the user interface. We can cache the results in
  memcache and once there we avoid a datastore hit as long as the data is
  available in the cache. Here is the code that handles looking up votes and
  caching the results: </p>

<pre class="prettyprint">def voted(quote, user):
  """Returns the value of a users vote on the specified quote, a value in [-1, 0, 1]."""
  val = 0
  if user:
    memcachekey = "vote|" + user.email() + "|" + str(quote.key().id())
    val = memcache.get(memcachekey)
    if val is not None:
      return val
    vote = Vote.get_by_key_name(key_names = user.email(), parent = quote)
    if vote is not None:
      val = vote.vote
      memcache.set(memcachekey, val)
  return val
</pre>
<h2 id="jquery">
  AJAX Libraries API
</h2>
<p>
  There are other optimizations that you can also do to save your application
  bandwidth. One of the easiest is to have Google host your Javascript library
  for you. The interactive part of the web pages for Overheard is done using
  jQuery and instead of adding the jQuery source to the application I employed
  Google's AJAX Libraries API. The AJAX Libraries API is a content distribution
  network and loading architecture for the most popular, open source JavaScript
  libraries. While this application uses jQuery you can also benefit if you use
  Prototype, script.aculo.us, Dojo, or any of the other JavaScript libraries in
  the growing list that the AJAX Libraries API supports.  </p>
<h2 id="html5">
  HTML5
</h2>
<p>
  The last optimization you can make is to create a clean user interface for
  your application in HTML 5. Doing so will allow your application to run not
  only on desktops but also on the latest mobile devices. That saves you effort
  from having to build a custom client for multiple mobile devices, and that
  should be a huge time savings.  </p>
<h2 id="summary">
  Summary
</h2>
<p>
  The Apache 2 licensed source for this application is available in the <a
      href="http://code.google.com/p/google-app-engine-samples/source/browse/#svn/trunk/overheard">Google
      App Engine Samples project on code.google.com</a>. A live version
  of this sample application is running at
  <a href="http://just-overheard-it.appspot.com">http://just-overheard-it.appspot.com</a>.
  </p>


</div></div>
   
      </div><!-- end gc-pagecontent -->
   </div><!-- end gooey wrapper -->

    </div> <!-- end codesite content -->

<div id="gc-footer" dir="ltr">
  <div class="text">
    
      <div class="notice"><div id="notice" style="text-align: center; border: 1em 0em 1em 0em">
  Except as otherwise <a
  href="http://code.google.com/policies.html#restrictions">noted</a>,
  the content of this page is licensed under the <a rel="license"
  href="http://creativecommons.org/licenses/by/2.5/">Creative Commons
  Attribution 2.5 License</a>, and code samples are licensed under the
  <a rel="license" href="http://www.apache.org/licenses/LICENSE-2.0">Apache
  2.0 License</a>.
<!-- <rdf:RDF xmlns="http://web.resource.org/cc/" 
              xmlns:dc="http://purl.org/dc/elements/1.1/"
              xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
  <Work rdf:about="">
    <license rdf:resource="http://creativecommons.org/licenses/by/2.5/" />
  </Work>
  <License rdf:about="http://creativecommons.org/licenses/by/2.5/">
    <permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
    <permits rdf:resource="http://web.resource.org/cc/Distribution"/>
    <requires rdf:resource="http://web.resource.org/cc/Notice"/>
    <requires rdf:resource="http://web.resource.org/cc/Attribution"/>
    <permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
  </License>
</rdf:RDF> -->
</div>
Java is a registered trademark of Sun Microsystems, Inc.</div>
    
    &copy;2009 Google -
    <a href="http://code.google.com">Code Home</a> -
    <a href="http://www.google.com/accounts/TOS">Terms of Service</a> -
    <a href="http://www.google.com/privacy.html">Privacy Policy</a> -
    <a href="http://code.google.com/more">Site Directory</a>
    <br /> <br />
    Google Code offered in:
    <a href="http://code.google.com/intl/en">English</a> -
    <a href="http://code.google.com/intl/es">Español</a> -
    <a href="http://code.google.com/intl/ja">日本語</a> -
    <a href="http://code.google.com/intl/ko">한국어</a> -
    <a href="http://code.google.com/intl/pt-BR">Português</a> -
    <a href="http://code.google.com/intl/ru">Pусский</a> -
    <a href="http://code.google.com/intl/zh-CN">中文(简体)</a> -
    <a href="http://code.google.com/intl/zh-TW">中文(繁體)</a>
  </div>
</div><!-- end gc-footer -->

</div><!-- end gc-containter -->

<script type="text/javascript">CODESITE_CSITimer['load'].tick('ats');</script>
<script src="../../js/codesite_tail.pack.04102009.js" type="text/javascript"></script>






  </body>
</html>


